-- Copyright (c) 2015,2016 Lucky Byte, Inc.
--
-- Summary : POS 009.1 内部交易处理(助农代付)
--
-- Author  : 吴小虎 <xiaohu@lucky-byte.com>
-- Since   : 2016.7.17
--
-- History
--  * 2016.7.17 : 创建此文件
--  * 2016.8.30 : 隔日不能代付
--

-- 通过助农代付终端查询助农取款代付配置
--
local _tz1_query_p2 = function(tz1_meta)
    local _table = jdbc.select([[
        select * from pay_p2 where tz1_termid = ?
    ]], reqt.term.get('uuid'))

    if _table.count == 0 then
        notify.error('助农代付终端[%s]未配置助农取款代付产品，请检查.',
            reqt.term.get('termid'))
        return '03'
    end
    if _table.count > 1 then
        logger.error('助农取款代付配置错误，代付终端[%s][%s]存在于多条配置记录中.',
            reqt.term.get('termid'), reqt.term.get('uuid'))
        return '01'
    end
    local _p2_record = _table.get_record(0)
    tz1_meta.p2_record = _p2_record

    logger.trace('查询到此代付终端的助农取款代付配置[%s].', _p2_record.get('uuid'))

    local _requires = {
        t60_fee         = '代付手续费',
        p2_fund         = '代理商资金池',
        fee_take_type   = '手续费收取方式',
        fund_lack_proc  = '资金池余额不足时处理方式',
        acct_no         = '代付收款账号',
        pay_chnl        = '代付渠道',
        pay_merid       = '代付渠道商户',
    }
    for _key, _name in pairs(_requires) do
        local _v = _p2_record.get(_key)
        if not _v or string.trimlen(tostring(_v)) == 0 then
            notify.error('助农取款代付配置[%s]错误，[%s]为空，请检查.',
                _p2_record.get('uuid'), _name)
            return resp.respkt('01')
        end
    end
    -- 身份认证要素(证件号码或姓名)必须至少提供一项
    if  string.trimlen(_p2_record.get('acct_name')) == 0 and
        string.trimlen(_p2_record.get('acct_cred_no')) == 0 then
        notify.error(
            '助农代付配置[%s]错误，收款账户身份认证信息必须至少提供姓名或证件号码中的一项.',
            _p2_record.get('uuid'))
        return resp.respkt('01')
    end
    return '00'
end


-- 解析助农取款代付报文 48 域
--
local _tz1_split_f48 = function(tz1_meta)
    local _priv = reqt.packet.get('48')
    if not _priv then
        logger.error('终端上送代付报文缺少[48]域，不能查询原助农取款交易信息.')
        return '10'
    end
    if string.len(_priv) < 166 then
        logger.error('终端上送代付报文[48]域数据长度[%s]错误.', string.len(_priv))
        return '10'
    end

    tz1_meta.f48 = {
        usage    = string.sub(_priv, 1, 2),      -- 用法
        _        = string.sub(_priv, 3, 4),      -- 用户号码类型
        _        = string.sub(_priv, 5, 44),     -- 用户号码
        _        = string.sub(_priv, 45, 48),    -- 用户号码地区编码
        _        = string.sub(_priv, 49, 52),    -- 用户号码附加地区编码
        _        = string.sub(_priv, 53, 58),    -- 缴费月份
        _        = string.sub(_priv, 59, 75),    -- 支付区间
        data_len = string.sub(_priv, 76, 78),    -- 附加子域长度
        merno    = string.sub(_priv, 79, 93),    -- 原商户编号
        termid   = string.sub(_priv, 94, 101),   -- 原终端编号
        batchnum = string.sub(_priv, 102, 107),  -- 原始交易批次号
        tracenum = string.sub(_priv, 108, 113),  -- 原始交易POS流水号
        authcode = string.sub(_priv, 114, 119),  -- 原始交易授权号
        retrefno = string.sub(_priv, 120, 131),  -- 检索参考号
        trtime   = string.sub(_priv, 132, 135),  -- 原始交易日期
        pri_acct = string.sub(_priv, 136, 154),  -- 原交易主账号
        tramt    = string.sub(_priv, 155, 166),  -- 原交易金额
    }
    return '00'
end


-- 通过代付报文中 48 域查询原助农取款交易流水记录
--
local _tz1_query_t60 = function(tz1_meta)
    local _table = jdbc.select([[
        select * from pay_trlist
        where
            sys_time::date = CURRENT_DATE and
            status = 0 and chnl = 1 and incoming = true and
            json_extract_path_text(i_pkt, '4')  = ? and
            json_extract_path_text(i_pkt, '11') = ? and
            json_extract_path_text(i_pkt, '41') = ? and
            json_extract_path_text(i_pkt, '42') = ? and
            json_extract_path_text(o_pkt, '37') = ? and
            json_extract_path_text(o_pkt, '39') = '00'
    ]], tz1_meta.f48.tramt,  tz1_meta.f48.tracenum,
        tz1_meta.f48.termid, tz1_meta.f48.merno, tz1_meta.f48.retrefno)

    if _table.count == 0 then
        notify.error('通过代付交易报文信息未查询到对应的助农取款交易，不允许代付.')
        return '14'
    end
    assert(_table.count == 1, '通过代付报文查询到多笔原助农取款交易.')

    tz1_meta.t60_record = _table.get_record(0)
    logger.trace('查询到此助农代付交易对应的原助农取款交易为[%s].',
        tz1_meta.t60_record.get('uuid'))
    return '00'
end


-- 通过助农取款流水查询助农取款代付订单
--
local _tz1_query_p2_order = function(tz1_meta)
    local _table = jdbc.select([[
        select * from pay_p2_order where p2_uuid = ? and t60_uuid = ?
    ]], tz1_meta.p2_record.get('uuid'), tz1_meta.t60_record.get('uuid'))

    if _table.count == 0 then
        notify.error('未查询到原助农取款交易创建的代付订单，不能继续代付，请检查.')
        return '05'
    end
    assert(_table.count == 1, '原助农取款交易创建了多笔代付订单?')

    tz1_meta.p2_order = _table.get_record(0)
    logger.trace('查询到此助农取款代付交易对应的代付订单为[%s].',
        tz1_meta.p2_order.get('uuid'))

    -- 立即更新已处理标志，避免后续的重复代付
    local _table = jdbc.select([[
        update pay_p2_order as o1 set received = true from (
            select received from pay_p2_order where uuid = ? for update
        ) as o2
        where uuid = ?
        returning o1.received as new_received, o2.received as old_received
    ]], tz1_meta.p2_order.get('uuid'), tz1_meta.p2_order.get('uuid'))

    -- 检查订单已处理标志，一笔订单不允许多次代付
    local _record = _table.get_record(0)
    if _record.get('old_received') then
        notify.info('助农代付订单[%s]已被受理过，忽略重复(也可能是延迟)的代付交易.',
            tz1_meta.p2_order.get('uuid'))
        return '13'
    end
    if not _record.get('new_received') then
        notify.error('更新助农取款代付订单[%s]已受理标志失败，代付失败.',
            tz1_meta.p2_order.get('uuid'))
        return '01'
    end
    return '00'
end


-- 从资金池扣除手续费
--
local _tz1_take_fee_from_fund = function(tz1_meta, tramt, p2_fee)
    -- 查询和修改资金池，在一个事务中处理
    jdbc.begin()

    -- 查询助农代付配置中的资金池，查询结果将被锁定修改
    local _table = jdbc.select([[
        select * from pay_p2_fund where uuid = ? for update
    ]], tz1_meta.p2_record.get('p2_fund'))

    -- 查询结果几乎不可能为空，因为数据表设计上有限制
    if _table.count == 0 then
        notify.error('助农取款代付配置[%s]中资金池[%s]无效.',
            tz1_meta.p2_record.get('uuid'),
            tz1_meta.p2_record.get('p2_fund') or '')
        return '01'
    end
    local _p2_fund = _table.get_record(0)

    -- 如果资金池余额足以支付手续费，则直接扣除手续费即可
    local _remains = _p2_fund.get('fund_remains')
    if _remains >= p2_fee then
        logger.debug('资金池余额[%s]足以支付手续费[%s]，将从资金池余额扣取手续费...',
            _remains, p2_fee)

        -- 扣取资金池手续费时，需要记录资金变动等信息
        if not core.p2_fund.take_from({
            p2_fund_uuid = _p2_fund.get('uuid'),
            amt = p2_fee,
            merno = tz1_meta.f48.merno,
            termid = tz1_meta.f48.termid,
        }) then
            jdbc.rollback()
            logger.error('从资金池扣取手续费失败，代付失败.')
            return '01'
        end
        jdbc.commit()   -- 结束数据库处理，提交事务
        return '00', tramt, 1
    end

    -- 如果资金池余额不足，则根据配置进行不同的处理
    local _fund_lack_proc = tz1_meta.p2_record.get('fund_lack_proc')

    -- 转从代付金额中扣取手续费
    if tonumber(_fund_lack_proc) == 4 then
        jdbc.rollback()
        logger.info('资金池余额[%s]不足以支付手续费[%s]，根据配置，转从代付金额中扣取手续费.',
            _remains, p2_fee)
        return '00', tramt - p2_fee, 2
    end

    -- 透支资金池，超额度时不允许代付交易
    if tonumber(_fund_lack_proc) == 3 then
        logger.info('资金池余额[%s]不足以支付手续费[%s]，根据配置，将透支资金池支付手续费.',
            _remains, p2_fee)

        local _overdraw = _p2_fund.get('overdraw')
        if not _overdraw then
            jdbc.rollback()
            notify.warn('助农取款代付配置[%s]错误[未配置资金池透支额度].',
                tz1_meta.p2_record.get('uuid'))
            return '01'
        end
        if _overdraw < 0.0 then
            jdbc.rollback()
            notify.warn('助农取款代付配置[%s]错误[资金池透支额度[%s]为负数].',
                tz1_meta.p2_record.get('uuid'), _overdraw)
            return '01'
        end
        -- 如果资金池余额加上透支额度足以支付手续费，则从资金池余额中扣取手续费
        -- 这将导致资金池余额小于 0
        if _remains + _overdraw < p2_fee then
            jdbc.rollback()
            notify.error(
                '资金池余额[%s] + 透支额度[%s] < 手续费[%s]，根据配置，不允许继续代付.',
                _remains, _overdraw, p2_fee)
            return '07'
        end
        logger.debug(
            '资金池余额[%s] + 透支额度[%s] >= 手续费[%s]，从资金池余额扣取手续费...',
            _remains, _overdraw, p2_fee)

        if not core.p2_fund.take_from({
            p2_fund_uuid = _p2_fund.get('uuid'),
            amt = p2_fee,
            merno = tz1_meta.f48.merno,
            termid = tz1_meta.f48.termid,
        }) then
            logger.error('从资金池扣取手续费失败，代付失败.')
            jdbc.rollback()
            return '01'
        end
        jdbc.commit()   -- 结束数据库处理，提交事务
        return '00', tramt, 1
    end

    -- 不允许助农取款交易 或 允许助农取款交易，但不允许代付交易
    jdbc.rollback()
    notify.error('资金池[%s]余额[%s]不足以支付手续费[%s]，根据配置，不允许继续代付.',
        _p2_fund.get('name'), _remains, p2_fee)
    return '07'
end


-- 通过全渠道发送代付交易
--
local _tz1_pay_by_3 = function(tz1_meta, pay_amt)
    local _071_packet = lib071.new_packet()

    -- 代付报文常数信息
    _071_packet.set('txnType', '12')
    _071_packet.set('txnSubType', '00')
    _071_packet.set('bizType', '000401')
    _071_packet.set('txnTime', os.date('%Y%m%d%H%M%S'))
    _071_packet.set('accessType', '0')
    _071_packet.set('txnAmt', round(pay_amt * 100, 0))

    -- 填充报文中商户相关的信息
    _071_packet.set('merId', tz1_meta.pay_merch.get('merno'))
    _071_packet.set('orderId', core.order_id())

    -- 收款账号
    _071_packet.set('accType', '01')
    _071_packet.set('accNo', tz1_meta.p2_record.get('acct_no'))
    _071_packet.set('termId', tz1_meta.f48.termid)

    -- 构建身份验证信息字符串
    local  _custinfo = core.acp_aux.build_custinfo({
        acct_name = tz1_meta.p2_record.get('acct_name'),
        cred_no   = tz1_meta.p2_record.get('acct_cred_no'),
        cred_type = tz1_meta.p2_record.get('acct_cred_type')
    })
    _071_packet.set('customerInfo', _custinfo)

    -- 发送报文
    local _fowd = lib071.forward({
        uuid = reqt.uuid, packet = _071_packet, indirect = true,
        merch = tz1_meta.pay_merch
    })
    if not _fowd or not _fowd.respkt then
        return '11'
    end
    if not lib071.respkt_ok(_fowd.respkt) then
        return _fowd.rescode and _fowd.rescode or '11'
    end
    logger.trace('银联 ACP 代付交易响应成功，需进一步等待后台通知确定此交易最终状态...')
    return '00'
end


-- 通过配置发送代付交易
local _tz1_pay_with_amt = function(tz1_meta, pay_amt)
    if pay_amt <= 0.0 then
        notify.error('实际代付金额[%s]小于或等于[0.0]，不能代付.', pay_amt)
        return '05'
    end
    logger.info('实际代付金额[%s]元...', pay_amt)

    local _pay_chnl  = tz1_meta.p2_record.get('pay_chnl')
    local _pay_merid = tz1_meta.p2_record.get('pay_merid')

    -- 查询代付商户信息
    local _table = jdbc.select([[
        select * from pay_merch where chnl = ? and uuid = ?
    ]], _pay_chnl, _pay_merid)

    -- 如果没有查询到代付商户
    if _table.count == 0 then
        notify.error('未能查询到代付商户[%s]的信息，不能代付.', _pay_merid)
        return '01'
    end
    tz1_meta.pay_merch = _table.get_record(0)

    -- 通过全渠道发送代付交易
    local _pay_ret
    if _pay_chnl == 3 then
        _pay_ret = _tz1_pay_by_3(tz1_meta, pay_amt)
    else
        notify.error('平台不支持通过代付渠道[%s]进行代付，请检查配置.', _pay_chnl)
        return '01'
    end

    -- 更新订单代付交易流水
    local _status = _pay_ret == '00' and 2 or 3
    local _notes  = _pay_ret == '00' and '代付交易成功，尚未确认' or '代付交易失败，尚未确认'
    jdbc.update([[
        update pay_p2_order set
            status = ?, t40_uuid = ?, notes = ?
        where uuid = ?
    ]], _status, reqt.uuid, _notes, tz1_meta.p2_order.get('uuid'))
    return _pay_ret
end


-- 通过配置执行代付
--
local _tz1_pay = function(tz1_meta)
    -- 手续费在登记订单时已计算
    local _p2_fee = tz1_meta.p2_order.get('p2_fee')
    if _p2_fee < 0 then
        logger.error('手续费[%s]无效，停止代付.', _p2_fee)
        return '01'
    end
    -- 原交易金额
    local _tramt = tonumber(tz1_meta.f48.tramt) / 100

    -- 代付金额，手续费扣取方式
    local _pay_amt = -1
    local _fee_take_from = nil

    -- 根据手续费收取方式配置，计算代付金额
    --  1: 从资金池扣取
    --  2: 从代付金额扣取
    local _fee_take_type = tz1_meta.p2_record.get('fee_take_type')
    if tonumber(_fee_take_type) == 1 then
        logger.trace('根据配置，将从资金池扣取手续费...')
        _rescode, _pay_amt, _fee_take_from =
            _tz1_take_fee_from_fund(tz1_meta, _tramt, _p2_fee)
        if _rescode ~= '00' then
            return _rescode
        end
    elseif tonumber(_fee_take_type) == 2 then
        logger.trace('根据配置，将从代付金额中扣取手续费...')
        _pay_amt = _tramt - _p2_fee
        _fee_take_from = 2
    else
        notify.error('助农取款代付[%s]配置错误[手续费收取方式[%s]无效]',
            tz1_meta.p2_record.get('uuid'), _fee_take_type)
        return '01'
    end

    -- 更新订单手续费收取方式，后续可能需要归还
    jdbc.update([[
        update pay_p2_order set fee_take_from = ?, p2_fund_uuid = ?
            where uuid = ?
    ]], _fee_take_from, tz1_meta.p2_record.get('p2_fund'),
        tz1_meta.p2_order.get('uuid'))

    -- 对代付金额进行二次检查
    if _pay_amt > _tramt then
        notify.fatal('实际代付金额[%s]大于原交易金额[%s], 不能代付',
            _pay_amt, _tramt)
        if _fee_take_from == 1 then
            core.p2_fund.give_back({
                p2_fund_uuid = tz1_meta.p2_record.get('p2_fund'),
                amt = _p2_fee,
                merno = tz1_meta.f48.merno,
                termid = tz1_meta.f48.termid,
            })
        end
        return '05'
    end

    -- 发送代付交易，如果交易失败，则归还手续费
    local _rescode = _tz1_pay_with_amt(tz1_meta, _pay_amt)
    if _rescode ~= '00' then
        if _fee_take_from == 1 then
            logger.trace('代付交易失败，归还手续费到资金池.')
            core.p2_fund.give_back({
                p2_fund_uuid = tz1_meta.p2_record.get('p2_fund'),
                amt = _p2_fee,
                merno = tz1_meta.f48.merno,
                termid = tz1_meta.f48.termid,
            })
        end
    end
    return _rescode
end


-- 构造 POS 009.1 代付成功响应报文
--
local _tz1_build_respkt = function()
    local _respkt = resp.respkt('00')

    _respkt.set('3',  reqt.packet.get('3'))
    _respkt.set('4',  reqt.packet.get('4'))
    _respkt.set('11', reqt.packet.get('11'))
    _respkt.set('12', os.date('%H%M%S'))
    _respkt.set('13', os.date('%m%d'))
    _respkt.set('15', os.date('%m%d'))
    _respkt.set('25', reqt.packet.get('25'))
    _respkt.set('37', os.date('%y%m%d%H%M%S'))
    _respkt.set('41', reqt.packet.get('41'))
    _respkt.set('42', reqt.packet.get('42'))
    _respkt.set('49', reqt.packet.get('49'))

    -- 添加报文 MAC 域
    -- 这里不再检查 工作密钥 等信息，因为在交易开始时已经检查过
    local _keys = core.json.decode(reqt.term.get('keys'))
    lib091.set_mac(_respkt, reqt.term.get('tmk'),
        _keys.mak, reqt.term.get('macalgo'))
    return _respkt
end


-- 助农代付交易
--
-- 处理流程：
--  1. 通过终端号查询助农取款代付配置，得到唯一记录，如果查询不到，则失败;
--  2. 通过代付报文查询原助农取款交易，得到唯一记录，如果查询不到则失败；
--  3. 通过订单配置和助农取款交易流水查询助农取款代付订单，得到唯一记录，如果查询不到则失败；
--  4. 自此，所有关系验证完成，开始执行代付；
--
local _inner_proc_tz1 = function()
    logger.debug('开始处理 POS 009.1 助农代付交易...')

    -- 创建一个名字空间，用于存储中间数据
    local _tz1_meta = {}

    -- 1. 查询助农取款代付配置
    local _rescode = _tz1_query_p2(_tz1_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 解析代付报文 48 域
    local _rescode = _tz1_split_f48(_tz1_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 2. 通过代付报文 48 域查询原助农取款交易流水
    local _rescode = _tz1_query_t60(_tz1_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 3. 检查原助农取款流水是否创建了助农取款代付订单
    local _rescode = _tz1_query_p2_order(_tz1_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end

    -- 4. 所有条件都满足，开始执行代付
    local _rescode = _tz1_pay(_tz1_meta)
    if _rescode ~= '00' then
        return resp.respkt(_rescode)
    end
    return _tz1_build_respkt()
end


return _inner_proc_tz1
